<!DOCTYPE html>
<html lang="zh_CN">
<head>
    <meta charset="UTF-8">
    <title>详细说明-增删除改查</title>
    <base href="/">
    <script src="js/common/jquery.js"></script>
    <script src="css/bootstrap/js/bootstrap.bundle.min.js"></script>
    <script src="js/common/jquery-datetime-picker.js"></script>
    <script src="js/common/common-functions.js"></script>
    <script src="js/common/auto-add-event.js"></script>
    <script src="js/common/ag-grid-community.js"></script>
    <script src="js/common/dayjs.min.js"></script>
    <link href="css/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="css/jquery-datetime-picker.css" rel="stylesheet">
    <link href="css/common.css" rel="stylesheet">
    <link href="css/ag-grid.css" rel="stylesheet">
    <link href="css/ag-theme-alpine.css" rel="stylesheet">
    <style>
        #testGrid {
            height: 65vh;
        }

        #debugDialog .modal-body {
            position: relative;
        }

        #addNewTestData {
            --bs-offcanvas-zindex: 2055;
            --bs-offcanvas-height: 500px;
        }

        .sex-form {
            align-items: end;
            justify-content: flex-start;
            display: flex;
        }

        #searchForm .col-auto {
            padding: 0 calc(var(--bs-gutter-x) * 0.1);
        }
    </style>
</head>
<body>
<nav class="navbar  common-nav-margin-bottom navbar-expand-lg bg-primary">
    <div class="container">
        <a href="/">
            <img src="asserts/common/mybatis-logo.png" alt="Bootstrap" width="90" height="24">
        </a>
        <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
            <div class="navbar-nav">
                <a class="nav-link" href="/">Home</a>
            </div>
        </div>
        <div class="d-flex" role="search">
            <button class="btn btn-info" type="submit" id="openDebugButton" data-detail-open-debug>打开调试</button>
        </div>
    </div>

    </div>
</nav>

<div class="container-fluid page-layout">
    <aside class="aside-menu">
        <ul class="list-unstyled">
            <li><a class="aside-menu-link" href="javascript:void(0) "
                   onclick="scrollToElementById('section-0')">详细说明</a></li>
            <li><a class="aside-menu-link" href="javascript:void(0) "
                   onclick="scrollToElementById('section-1')">删改</a></li>
            <li><a class="aside-menu-link" href="javascript:void(0) "
                   onclick="scrollToElementById('section-2')">增</a></li>
            <li><a class="aside-menu-link" href="javascript:void(0) "
                   onclick="scrollToElementById('section-3')">查</a>
                <ul class="list-unstyled secondary">
                    <li><a class="aside-menu-link" href="javascript:void(0) "
                           onclick="scrollToElementById('section-3-1')">最基本的</a></li>
                    <li><a class="aside-menu-link" href="javascript:void(0) "
                           onclick="scrollToElementById('section-3-2')">一对一</a></li>
                    <li><a class="aside-menu-link" href="javascript:void(0) "
                           onclick="scrollToElementById('section-3-3')">一对多</a></li>
                    <li><a class="aside-menu-link" href="javascript:void(0) "
                           onclick="scrollToElementById('section-3-4')">属性自动映射</a></li>
                </ul>
            </li>
            <li><a class="aside-menu-link" href="javascript:void(0) "
                   onclick="scrollToElementById('section-4')">动态条件</a>

            </li>
            <li><a class="aside-menu-link" href="javascript:void(0) "
                   onclick="scrollToElementById('section-5')">ognl表达式</a>
                <ul class="list-unstyled secondary">
                    <li><a class="aside-menu-link" href="javascript:void(0) "
                           onclick="scrollToElementById('section-5-1')">在mapper中调用java中的静态方法</a></li>
                    <li><a class="aside-menu-link" href="javascript:void(0) "
                           onclick="scrollToElementById('section-5-2')">插值</a></li>
                </ul>
            </li>
            <li><a class="aside-menu-link" href="javascript:void(0) "
                   onclick="scrollToElementById('section-6')">重复使用sql片段</a>
                <ul class="list-unstyled secondary">
                    <li><a class="aside-menu-link" href="javascript:void(0) "
                           onclick="scrollToElementById('section-6-1')">引用sql片断</a></li>
                    <li><a class="aside-menu-link" href="javascript:void(0) "
                           onclick="scrollToElementById('section-6-2')">继承</a></li>
                </ul>
            </li>
        </ul>
    </aside>
    <main>
        <h1 id="section-0">详细说明</h1>
        <p class="intent-2">针对4种不同类型的 sql,
            分别对应4种不同的xml标签<code>insert</code>,<code>delete</code>,<code>update</code>,<code>select</code>,但是<code>insert</code>,<code>delete</code>,<code>update</code>
            可以看成一样的，即使在insert标签中写一个update也不会报错，但是不推荐。</p>

        <div lazy-src src="/mapper-resource/user/UserDetail.mapper.xml" class="small-light-height-pre ">
        </div>
        <p class="intent-2">关于参数的问题，具体内容会在 <a
                href="pages/parameter/parameter-index.html">参数这部分说明</a>，为方便看例子中的代码，现在只要知道写法就是
            <code>#{变量名,jdbcType=数据库类型}</code>。 </p>

        <p>其中的变量名：</p>
        <ul>
            <li>当mapper 对应的方法入参是一个自定义类型的对象时，变量名就是这个对象的属性名；</li>
            <li>当mapper 对应的方法入参是多个基本数据类型,基本类型的包装类型或者String，那么变量名就是 接口方法定义时用的名字（jdk
                1.8 +）
            </li>
            <li><a href="pages/parameter/parameter-index.html">当然情况不只以上两种</a></li>
        </ul>
        <p class="intent-2">变量名<strong class="emphasize">是基于get/set方法和反射的</strong>。</p>
        <ul>
            <li>如果有 sex 这个属性，但没有写 getSex 这个方法，mybatis会尝试使用反射获取值，反射没有找到对应属性就会报错。
            </li>
            <li>如果没有sex这个属性，但是有getSex这个方法，mybatis 即认为象中有这个属性。</li>
        </ul>

        <p class="intent-2">其中的数据库类型，指的不是真正的数据库数据类型，指的是
            <code>org.apache.ibatis.type.JdbcType</code>,
            它不是完全与数据库类型对应的，比如 Oracle 中的
            number, Mysql中的 bigint, int 都可以用 NUMERIC, 如果有特殊的类型，就需要自定义类型处理器了。当然大部分都是可以对应上的。
        </p>

        <h2 id="section-1">delete/update</h2>
        <p>删改似乎没有什么需要注意的地方！！ </p>
        <h2 id="section-2">insert</h2>
        <p class="intent-2">对于insert 操作，针对不同数据库是可以进行自动生成ID，之后将生成的ID填充回插入对象的。注意
            insert 标签中的 <code>keyProperty="id"
                keyColumn="id" useGeneratedKeys="true"</code>
            这3个属性，userGeneratedKeys 代表使用数据库生成的主键，keyColumn 代表在哪个列上使用， keyProperty
            代表对应的java对象中的属性名称，sql中就不用出现ID字段了。</p>
        <p class="intent-2">但是生成主键的功能对数据库是有要求的，mysql可以使用自增列，本例中的数据库设置也是 <strong>ID
            列使用的自增主键</strong>，
            所有这样写没有问题。 Oracle 中没有自增列，就要用其他写法，要用到 selectKey 标签。大概如下： </p>
        <div lazy-src src="/asserts/samples/generated-key.xml"></div>

        <button type="button" class="btn btn-link" id="openInsertDemoBtn">打开插入例子</button>
        <p>相关文件：</p>
        <button type="button" file-src="/mapper-resource/user/UserDetail.mapper.xml" show-src
                src-name="UserDetail.mapper.xml"
                class="btn btn-link">mapper/user/UserDetail.mapper.xml
        </button>
        <br>
        <button type="button" file-src="/java-resource/user/dto/UserDetail.java" show-src
                src-name="UserDetail.java"
                class="btn btn-link">org/issaclu/springbootmybatis/user/dto/UserDetail.java
        </button>
        <br>
        <button type="button" file-src="/java-resource/user/mapper/UserDetailMapper.java" show-src
                src-name="UserDetailMapper.java"
                class="btn btn-link">org/issaclu/springbootmybatis/user/dto/UserDetailMapper.java
        </button>
        <h2 id="section-3">Select</h2>

        <p class="intent-2">这里不介绍查询语句怎么写，只介绍在语句查询之后，怎么将数据库结果集（ResultSet）映射（转换）为 目标
            java 对象。
            对于mybatis 来说，一个查询语句的结果字段来自几个表都无所谓，但是mybatis要知道结果集每个字段应该对应哪个类的哪个属性。
            这个工作大多数情况是通过编写 ResultMap 来完成的，当然有些情况也可以不编写 ResultMap
            ,但都针对极简单的场景。</p>

        以下通过3个情况说明怎么编写 ResultMap:
        <ul>
            <li><strong>最基本的</strong> 将结果集的每一条数据转为一个java对象</li>
            <li>将<strong>一对一</strong>表转为一个对象包含另一对象的情况，比如 user_info 与 user_detail 一一对应，
                现需要在查询成功后将一个数据转为 一个
                <button type="button" file-src="/java-resource/user/dto/Userinfo.java" show-src src-name="UserInfo.java"
                        class="btn btn-link">UserInfo
                </button>
                对象中包含一个
                <button type="button" file-src="/java-resource/user/dto/UserDetail.java" show-src
                        src-name="UserDetail.java"
                        class="btn btn-link">UserDetail
                </button>
                对象
            </li>
            <li>将<strong>一对多</strong>表转为一个对象包含另一个类型对象列表的情况， 比如一个 user_info 对应多条
                house_info
            </li>
        </ul>

        <h4 id="section-3-1">最基本的</h4>
        <p>有以下2个例子：</p>

        <ul>
            <li>通过ID查询唯一的数据，请查看
                <button type="button" file-src="/mapper-resource/user/UserInfoBasicQuery.mapper.xml" show-src
                        src-name="UserInfoBasicQuery.mapper.xml"
                        class="btn btn-link">UserInfoBasicQuery.mapper.xml
                </button>
                中的<code>getById</code> sql片段和 <code>ResultMap1</code> 部分;
                点击右上角
                <button type="button" class="btn btn-link" data-detail-open-debug>打开调试</button>
                ，再点击行后面的 <code>getById</code> ， 便可以运行这个例子
            </li>
            <li>通过名字查询用户数据，并且连同用户明细（user_detail） 数据一同查出，将每一行数据转为一个java对象，等到一个
                <button type="button" file-src="/java-resource/user/dto/UserInfoDetail.java" show-src
                        src-name="UserInfoDetail.java"
                        class="btn btn-link"> UserInfoDetail 集合;
                </button>
                请查看
                <button type="button" file-src="/mapper-resource/user/UserInfoBasicQuery.mapper.xml" show-src
                        src-name="UserInfoBasicQuery.mapper.xml"
                        class="btn btn-link">UserInfoBasicQuery.mapper.xml
                </button>
                中的<code>getUserWithDetailByName</code> sql片段和 <code>ResultMap2</code> 部分;
                点击下面的 <strong>查询</strong> 按钮进行测试
            </li>
        </ul>
        <div class=" mb-3 row">
            <label for="searchUserName" class="col-2 col-form-label" style="text-align: right">名字</label>
            <div class="col-4">
                <input type="text" class="form-control" id="searchUserName">
            </div>
            <div class="col-6">
                <button type="button" class="btn btn-primary" id="searchUserNameBtn">查询</button>
            </div>
        </div>
        <div class="small-light-height-pre can-max">
        <pre id="searchUserNamePre">

        </pre>
        </div>

        <p>以上两个例子注重看以下两点就可以</p>
        <ol>
            <li>select 标签上的resultMap 属性</li>
            <li>
                查询的结果不论是一个还是多个，指定resultMap中不用指定是用集合接收还是用单个对象接收，会根据对应的接口自动转换
            </li>
        </ol>

        <p class="intent-2">每个 <code>select</code> 标签都需要有 <code>resultMap</code> 或 <code>resultType</code> 属性,
            <a href="javascript:scrollToElementById('section-3-4')">resultType</a>是对通常情况下将数据转为java对象的简单写法,
            这个后说。这里主要讲 <code>resultMap</code>,<code>resultMap</code> 代表的是每一个列应该与对象的哪个属性对应。
            上面两个例子：
            <button type="button" file-src="/mapper-resource/user/UserInfoBasicQuery.mapper.xml" show-src
                    src-name="UserInfoBasicQuery.mapper.xml"
                    class="btn btn-link">UserInfoBasicQuery.mapper.xml
            </button>
        </p>
        <p class="intent-2">当 <code>select</code> 标签中的sql 执行结束后，通过 <code>resultMap属性</code>设置的值找到对应的<code>resultMap标签</code>(id),
            然后按标签中的内容去构造java对象，
        </p>
        <div lazy-src src="/asserts/samples/basic-index-section-3-1-2.txt" class="small-light-height-pre">
        </div>

        <p class="intent-2"><code>resultMap </code> 中的 <code>id</code>加上文件顶的 <code>namespace</code> 属性需要是全局唯一的
        </p>
        <p class="intent-2"><code>resultMap </code> 中的 <code>type</code> 属性代表要转为哪个类型的对象 </p>
        <p class="intent-2"><code>resultMap </code> 中的子标签 <code>id</code>和 <code>result</code> 功能都是说明哪个列对应哪个属性，
            这两者之间的唯一不同是，id 元素对应的属性会被标记为对象的标识符，在比较对象实例时使用。
            这样可以提高整体的性能，尤其是进行缓存和嵌套结果映射（也就是连接映射）的时候。
            所以通常情况下设置数据表的主键为id ,但是在一些特殊情况下，也可能主键不能代表对象的唯一，比如一些聚合查询结果，这就要根据实际情况分析了
        </p>
        <p class="intent-2"><code>id/result</code> 中的 <code>column</code> 代表查询结果集中的列名.</p>
        <p class="intent-2"><code>id/result</code> 中的 <code>property</code> 代表java 对象的属性名。</p>
        <p class="intent-2"><code>id/result</code> 中的 <code>jdbcType</code>
            代表结果集中这一列的数据类型，注意，这个不要单纯的看成是表结构中列是什么类型，结果集就是什么类型。
            因为一些复杂的查询可能会将列的数据类型转换后再返回到java程序中。比如一个精度高的浮点型数据列，java
            程序中可能不对其计算，就可以在数据库中转为 字符再返回，这里 jdbcType=VARCHAR ,而不是NUMERIC.
            再比如数据库中的大文本类型对应的应该是 CLOB, 但是写VARCHAR 也没有问题， 具体可以看 <a
                    href="/pages/parameter/parameter-index.html">数据类型</a>。 </p>
        <p class="intent-2"><code>id/result</code> 中的 <code>javaType</code>, 代表java中的类名，一般的javabean mybatis
            是可以自动正常处理的，特殊的可以使用typeHandler</p>
        <p class="intent-2"><code>id/result</code> 中的 <code>typeHandler</code>是一个映射结果过程中的一个扩展点。按mybatis
            官方的解释， mybatis 希望有一个数据库可以完美的映射java 程序，
            但实际业务中总会有些想不到的情况，这些想不到的情况就可以使用typeHandler 解决。 typeHandler 接口会在 jdbc
            绑定参数，还有在从ResultSet 取值时调用，所以相对应的，java 代码中就要实现这两个过程，
            <button type="button" file-src="/java-resource/util/mybatistypehandle/SexTypeHandler.java" show-src
                    src-name="SexTypeHandler.java" class="btn btn-link">SexTypeHandler.java
            </button>
            是一个例子
        </p>

        <h4 id="section-3-2">一对一 <code>association</code></h4>

        <p class="intent-2">在处理一对一关联表时，可以像上面的例子，将两个表字段对应为一个包含所以对应属性的大对象，也可以更清晰一点，用主表对象包含一个关联表对象，
            比如
            <button type="button" file-src="/java-resource/user/dto/Userinfo.java" show-src src-name="UserInfo.java"
                    class="btn btn-link">UserInfo
            </button>
            对象中包含一个
            <button type="button" file-src="/java-resource/user/dto/UserDetail.java" show-src
                    src-name="UserDetail.java" class="btn btn-link">UserDetail
            </button>
            对象
        </p>

        <p class="intent-2">点击右上角
            <button type="button" class="btn btn-link" data-detail-open-debug>打开调试</button>
            ，再点击行后面的 <code>Demo1</code> ， 便可以运行这个例子
        </p>
        <div lazy-src src="/asserts/samples/basic-index-section-3-2-1.txt" class="small-light-height-pre"></div>
        <p class="intent-2">需要实现这种嵌套的结构，需要在 <code>resultMap</code> 中使用 <code>association </code>标签，
            这个标签的作用就是将结果集中的某些列映射到java 返回对象中的嵌套对象对象中去</p>
        <p class="intent-2"><code>property</code>代表嵌套对象的属性名</p>
        <p class="intent-2"><code>javaType</code>代表嵌套对象的类型</p>
        <p class="intent-2">外层对象使用过的 column 在内层对象中也是可以重复使用的，比如例子中的 <code>id</code></p>
        <p class="intent-2">通常这种对象的查询都是关联多表的查询，注意不要列名重复，小心使用 <code>select *</code></p>
        <h5>复用ResultMap</h5>
        <p class="intent-2">上面的写法是将外层对象与内存对象的映射写在同一个 <code>resultMap</code>中的，
            很多时候可能 <code>association</code>中的内容可能与其他 <code>resultMap</code>中的内容一样,这时候是可以复用其他
            <code>resultMap</code>的，
            下面这个例子中的 <code>resultMap</code>MainObjectResultMap 中的 <code>association</code> 指向的是另一个
            <code>ResultMap</code> InnerObjectResultMap,
            InnerObjectResultMap 也可以被其他 <code>select </code>使用，
            <button type="button" class="btn btn-link" data-detail-open-debug>打开调试</button>
            ，再点击行后面的 <code>Demo2</code> ， 便可以运行这个例子
        </p>
        <div lazy-src src="/asserts/samples/basic-index-section-3-2-2.txt" class="small-light-height-pre"></div>
        <p class="intent-2">可以看到结果与 Demo1是一样的</p>
        <h5>关联其他Select</h5>
        <p class="intent-2">上面两个例子是只是写法不同,查询的原理和效率都是一样的，Mybatis 还提供类似 hibernate
            的分步查询的方式，就是先执行一个查询，再根据查询结果，执行后面的关联查询</p>
        <p>
            <button type="button" class="btn btn-link" data-detail-open-debug>打开调试</button>
            ，再点击行后面的 <code>Demo3</code> ， 便可以运行这个例子
        </p>
        <div lazy-src src="/asserts/samples/basic-index-section-3-2-3.txt" class="small-light-height-pre"></div>
        <p class="intent-2"> 注意 <code>association</code> 中的 <code>select</code>, 它的值是另一个 <code>select</code>
            查询的的 <code>id</code></p>
        <p class="intent-2"> 注意 <code>association</code> 中的 <code>column</code>, 这代表给下一个要执行的select传递的参数，格式是：
            <code>column={prop1=col1, prop2=col2 ....}</code>,
            等号前面是下一要 <code>select</code> 中要用的参数名（<code>#{prop2}</code>）, 等号后面是前一个
            <code>select</code> 查询结果的列名，不是java 对象的属性名。 如果下一个要执行的 <code>select</code>
            要执行的sql只需要一个参数的话，可以简写为 <code>column=col1</code></p>
        <p class="intent-2"> 注意 <code>association</code> 中的 <code>fetchType</code>, <code>eager</code>代表立即执行下一个
            <code>select</code> ，对应的还可以设置为 <code>lazy</code>。
            设置为 lazy 后会等到java程序中访问了 association 指定的属性（property） 时才执行查询 </p>
        <p class="intent-2">后台中有一个关于懒加载(fetchType=lazy)的例子, <strong
                class="emphasize">但是这个例子不能被debug</strong>, 只能查看日志，因为debug时 IDE
            会收集断点处上下文所有对象的属性值，并且递归向下收集，
            这时候就会潜在的触发懒加载的相关对象，只能通过打印日志来验证与急加载的区别 </p>
        <p class="intent-2">
            <button type="button" class="btn btn-link" data-detail-open-debug>打开调试</button>
            ，再点击行后面的 <code>Demo3</code> ， 便可以运行这个例子, 但是这个例子在向前台响应结果时会出错，
            因为懒加载时mybatis 为了监控这个返回对象的各个方法是否被调用，返回给java程序的是一个代理对象，例子中直接将他返回到前台时，会经过spring
            处理，spring 处理不了这个对象就报错了
        </p>
        <pre style="width: 80vw;">
            com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory$EnhancedResultObjectProxyImpl and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.issaclu.springbootmybatis.user.dto.UserInfo_$$_jvstd4d_0["handler"])
        </pre>

        <p class="emphasize">
            效率问题N+1: 这种嵌套的查询的效率不如关联查询，因为会多次访问数据库
        </p>
        <p class="emphasize">
            代码表达: 如何向其他人表达出这是一个多次查询的Mapper? 在java代码中体现不出来 ，只能看 mapper 文件才知道，但调用者不一定这样做
        </p>
        <p class="emphasize">
            resultMap 复用减少了代码量，但增加了耦合度，这要大量的前期设计工作，而且mapper文件又不能像java
            一样表达出类型信息，从而让IDE帮识别这些问题，导致维护会越来越难
        </p>
        <h4 id="section-3-3">一对多 <code>collection</code></h4>
        <p class="intent-2">
            数据库中一对多的表通常在java中表现为 一个类型的对象包含另一个类型的对象的集合， mybatis 同样可以根据 <code>resultMap</code>
            完成这一工作
        </p>
        <div lazy-src src="/asserts/samples/basic-index-section-3-3-1.txt" class="small-light-height-pre"></div>
        <p class="intent-2">
            <button type="button" class="btn btn-link" data-detail-open-debug>打开调试</button>
            ，再点击行后面的
            <code>Demo5 </code> ，便可以运行这个例子
        </p>
        <p class="intent-2"><code>collection</code> 标签与 <code>association</code> 类似 ，只是将javaType 换为
            ofType，javaType
            一般情况下不需要设置
        </p>
        <p class="intent-2"><code>collection</code> 也可以实现 分步查询与 lazy 查询，
            <button type="button" class="btn btn-link" data-detail-open-debug>打开调试</button>
            ，再点击行后面的
            <code>Demo6</code> ，便可以运行这个例子. 注意， <code>fetchType=lazy</code> 会导致spring 处理响应值失败，需要查看日志
        </p>
        <h4 id="section-3-4">属性自动映射</h4>

        <p class="intent-2">前面总是说数据库字段设置到java属性的值需要使用 resultMap, mybatis
            也考虑到大多数情况是数据库字段的名字与属性名字是同名的，比如 <code>ID</code> 对应 <code>id</code>,
            在简单的查询情况下，resultMap 显得非常没有必要， 所以在这种列名与属性名同名（不区分大小写）的情况下可以使用
            resultType ,或者resultMap中同名的字段就可以不写了</p>
        <p class="intent-2">
            <button type="button" class="btn btn-link" id="resultTypeDemoBtn">resultType例子</button>
            ,
            <button type="button" class="btn btn-link" id="resultMapDemoBtn">resultMap省略同名部分的例子</button>
        </p>
        <div lazy-src src="/asserts/samples/basic-index-section-3-4-1.txt" class="small-light-height-pre"></div>
        <p>
            上面两个例子中可以看到列 <code>introduction_text</code> 和对应的属性 <code>introductionText</code>
            不同名，但仅仅差在命名风格上不同，也可以自动映射，
            这是因为可以在 <code>application.property</code> 文件中配置 <code>mybatis.configuration.map-underscore-to-camel-case=true</code>
        </p>
        <p class="intent-2">自动映射功能默认是有一定范围的，对于嵌套的 resultMap 是不执行自动映射的，因为存在一些潜在的风险,
            如： 一个关联多表的查询，
            多个表的结果列在同一行中都会出现，<span class="emphasize">可能会导致映射错误</span></p>
        <div lazy-src src="/asserts/samples/basic-index-section-3-4-2.txt" class="small-light-height-pre">
        </div>
        <p class="intent-2">下面这个查询如果 <code>Blob</code> 类型中也有一个属性叫 <code>username</code> ,可能就不对了，
            因为这个查询的username 是来自
            author 表的</p>

        <p class="intent-2">当然如果确定这种风险在代码中不存在，那么也可以开户对嵌套 resultMap 的自动映射 <code>mybatis.configuration
            .auto-mapping-behavior=FULL</code>, 也可以在 <code>resultMap</code>上针对个别 resultMap 单独设置</p>

        <p class="intent-2 emphasize">
            自动映射对于不嵌套的resultMap 没有问题，一但嵌套就很容易出问题，这时候的表现是 sql 请求正确，查询结果正确，但得到的
            java 对象不正确，比如下面的例子
        </p>
        <div lazy-src src="/asserts/samples/basic-index-section-3-4-3.txt" class="small-light-height-pre">
        </div>
        <p class="intent-2">在浏览器中输入地址： http://localhost:18080/user/queryAll3</p>
        <p class="intent-2">user_info 与 house_info 应该是一对多的关系，但是查询的结果变多了， 同一id的 user_info
            查出了多个</p>
        <h4 id="section-4">动态条件</h4>
        <p class="intent-2">有些时候一个sql 的where 条件需要根据入参的不同而变化的，
            下面这个例子就是如果参数为空，那么就去掉某个where条件</p>
        <p class="intent-2">
            <button type="button" class="btn btn-link" data-detail-open-debug>打开调试</button>
            , 点击
            Search 按钮， 查看后台日志中 sql 的where 条件
        </p>
        <div lazy-src src="/asserts/samples/basic-index-section-4-1-1.txt" class="small-light-height-pre">
        </div>
        <h4 id="section-5">OGNL表达式</h4>
        <p class="intent-2">ognl 表达式和 <code>#{变量名}</code>的作用类似，就是把sql 中的一部分用变量值替换掉，只不过
            <code>#</code>形式是将
            <code>#{变量名}</code>替换为 <code>?</code> ,然后在数据库中将真正值替换。而 ognl 表达式是在java程序中就将变量替换到
            sql 文中，
            所以 ognl 表达式在使用时要 <span class="emphasize">注意sql注入的问题</span></p>
        <p class="intent-2">ognl在mybatis的主要用途有两个</p>
        <ol>
            <li>在mapper 中调用 java中的静态方法</li>
            <li>使用插值 <code>${变量名}</code>替换 mapper 中的sql片断</li>
        </ol>
        <h5 id="section-5-1">在mapper中调用java中的静态方法</h5>
        <p class="intent-2">在动态条件的例子中 <code>
            test="@org.issaclu.springbootmybatis.util.Utils@isNotBlank(introductionText)"</code> 这部分就是在 mapper
            中调用java静态方法， 用于进行一些复杂的逻辑
            <button type="button" file-src="/java-resource/util/Utils.java" show-src
                  src-name="Utils.java" class="btn btn-link">Utils.java</button></p>
        <h5 id="section-5-2">插值</h5>
        <p class="intent-2">在动态条件的例子中 <code> t.introduction_text like concat('%',${introductionText},'%')</code>
            部分的 <code>${introductionText}</code>就是 ognl 的插值使用， 查看后台日志， 用 <code>#</code>传参，打印的是 <code>?</code>, 而用
            <code>$</code>传参打印的是真正的值</p>
        <h4 id="section-6">sql片断复用</h4>
        <p class="intent-2">例子： </p>
        <div lazy-src src="/mapper-resource/user/UserInfoReuseDemo2.mapper.xml" class="small-light-height-pre">

        </div>
        <p class="intent-2"> <button type="button" class="btn btn-link" data-detail-open-debug>打开调试</button> ,
            点击Search 2 进行测试</p>

        <h5 id="section-6-1">引用sql片断</h5>
        <p class="intent-2"> <code>include refid="user_and_house_sql"</code> 的作用就是将 <code>user_and_house_sql</code>
            这段sql 放在这个位置替换。不光是 sql 语句的select 部分可以替换，其他部分也可以，这个单纯是文本替换。
        </p>
        <p class="intent-2">引用sql片断时还可以使用增加一些额外的参数：例子中的<code>user_and_house_where</code> 就是如何向一段sql 片断传参数</p>
        <h5>resultMap复用</h5>
        <p class="intent-2">除了向前面说的， resultMap 可以在同一个mapper 文件中重复使用外，还可以在其他文件中补使用，在sql片断利用的例子中，
        <code>resultMap="org.issaclu.springbootmybatis.user.mapper.UserInfoBasicQueryMapper.UserWithHouseResultMap"</code>
        引用的就是其他mapper 文件中的 ResultMap , <strong>实际上，mapper 文件中的 所有 <code>id</code>都隐含了这个mapper文件的
            <code>namespace</code>, 要引用一个 sql 片断， resultMap , 一个 select ,无论在哪个文件，只要写
                <code>{namespace}.{id}</code></strong>都可以使用</p>
        <h5 id="section-6-2">resultMap 继承</h5>
        <div lazy-src src="/asserts/samples/basic-index-section-6-2-1.txt" class="small-light-height-pre">

        </div>

    </main>

    <!--    下面是弹窗部分-->
    <div class="modal fade" id="debugDialog" tabindex="-1" aria-labelledby="staticBackdropLabel"
         aria-hidden="true" data-bs-backdrop="static">
        <div class="modal-dialog modal-dialog-scrollable modal-fullscreen">
            <div class="modal-content">
                <div class="modal-header">
                    <h1 class="modal-title fs-5">测试数据</h1>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <div class="container-fluid">
                        <div class="row">
                            <div class="col-9">
                                <div class="container-fluid">
                                    <form class="row" id="searchForm">
                                        <div class="col-auto">
                                            <input type="text" class="form-control form-control-sm"
                                                   name="searchUserName"
                                                   placeholder="名字">
                                        </div>
                                        <div class="col-auto">
                                            <input type="text" class="form-control form-control-sm"
                                                   name="searchIntroduction"
                                                   placeholder="描述">
                                        </div>
                                        <div class="col-auto">
                                            <div class="form-check form-check-inline">
                                                <input class="form-check-input" type="radio" name="searchSex"
                                                       id="searchSex1"
                                                       value="MALE"
                                                >
                                                <label class="form-check-label" for="searchSex1">
                                                    男
                                                </label>
                                            </div>
                                            <div class="form-check form-check-inline">
                                                <input class="form-check-input" type="radio" name="searchSex"
                                                       id="searchSex2"
                                                       value="FEMALE">
                                                <label class="form-check-label" for="searchSex2">
                                                    女
                                                </label>
                                            </div>
                                            <div class="form-check form-check-inline">
                                                <input class="form-check-input" type="radio" name="searchSex"
                                                       id="searchSex3"
                                                       value>
                                                <label class="form-check-label" for="searchSex2">
                                                    -
                                                </label>
                                            </div>
                                        </div>
                                        <div class="col-auto">
                                            <input type="text" name="birthdayStart" id="birthdayStart" readonly
                                                   class="form-control form-control-sm">
                                        </div>
                                        <div class="col-auto">
                                            <input type="text" name="birthdayEnd" id="birthdayEnd" readonly
                                                   class="form-control form-control-sm">
                                        </div>
                                    </form>
                                </div>
                            </div>
                            <div class="col-3">
                                <button type="button" class="btn btn-primary" id="searchBtn">Search</button>
                                <button type="button" class="btn btn-primary" id="searchBtn2">Search 2</button>
                                <button type="button" class="btn btn-primary" id="reset">Reset</button>
                            </div>
                        </div>
                    </div>
                    <div id="testGrid" class="ag-theme-alpine">

                    </div>
                </div>
                <div class="modal-footer">
                    <div class="btn-group" role="group">
                        <button type="button" class="btn btn-primary" data-bs-toggle="offcanvas"
                                data-bs-target="#addNewTestData">新数据
                        </button>
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                    </div>
                </div>
            </div>
        </div>
    </div>


    <div class="offcanvas offcanvas-bottom" tabindex="-1"
         data-bs-backdrop="false"
         id="addNewTestData" aria-labelledby="offcanvasBottomLabel">
        <div class="offcanvas-header">
            <h5 class="offcanvas-title" id="offcanvasBottomLabel">添加数据</h5>
            <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
        </div>
        <form class="offcanvas-body small" id="newDataForm">
            <div class="row gy-2">
                <div class="col-4">
                    <label for="userName" class="form-label">名字</label>
                    <input type="text" id="userName" name="userName" class="form-control">
                </div>
                <div class="col-4">
                    <label for="age" class="form-label">年龄</label>
                    <input type="number" id="age" name="age" class="form-control">
                </div>
                <div class="col-4 sex-form">
                    <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="sex" value="MALE" id="sex1" checked>
                        <label class="form-check-label" for="sex1">
                            男
                        </label>
                    </div>
                    <div class="form-check form-check-inline">
                        <input class="form-check-input" type="radio" name="sex" value="FEMALE" id="sex2">
                        <label class="form-check-label" for="sex2">
                            女
                        </label>
                    </div>
                </div>
            </div>
            <br>
            <div class="row gy-2">
                <div class="col-4">
                    <label for="level" class="form-label">等级</label>
                    <select id="level" class="form-select" name="level" aria-label="Default select example">
                        <option selected value="0">选择等级</option>
                        <option value="1">1</option>
                        <option value="2">2</option>
                        <option value="3">3</option>
                    </select>
                </div>
                <div class="col-4">
                    <label for="birthday" class="form-label">出生日期</label>
                    <input type="text" id="birthday" name="birthday" readonly class="form-control">
                </div>
                <div class="col-4">
                    <label for="formFile" class="form-label">头像</label>
                    <input class="form-control" type="file" name="avatar" id="formFile">
                </div>
            </div>
            <br>
            <div class="row gy-2">
                <div class="col-12">
                    <label for="introductionText" class="form-label">介绍</label>
                    <textarea class="form-control" id="introductionText" name="introductionText" rows="3"></textarea>
                </div>
            </div>
            <br>
            <div class="row gy-2">
                <div class="col-12">
                    <button type="button" class="btn btn-primary" id="saveNewDataBtn">保存</button>
                </div>
            </div>
        </form>
    </div>
    <!--    一对一，对象包含弹窗-->
    <div class="alert alert-success" style="display: none" id="oneToOneAlert" role="alert">
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
        <h4 class="alert-heading">查询结果</h4>
        <pre class="alert-body" style="max-height: 60vh;">

        </pre>
        <hr>
        <p class="mb-0">注意 json 结构，外层是 user_info 数据， 内层 detail 的数据是 user_detail 的数据</p>
    </div>
</div>

<script>
    let gridApi;
    let gridOption;
    let addNewTestDataCanvas
    $(function () {
        lazyLoadSrc()
        initSrcEvent()
        // 绑定打开测试数据的对话窗
        const buttons = document.querySelectorAll('[data-detail-open-debug]');
        buttons.forEach(button => {
            button.addEventListener('click', openDebugDialog)
        })
        // 加载表格
        gridOption = initTestGrid()
        gridApi = gridOption.api
        // 新增数据的form
        addNewTestDataCanvas = new bootstrap.Offcanvas(document.querySelector('#addNewTestData'))
        // 新增数据的form中的时间控件
        $('#birthday').datetimepicker();
        // 保存新数据按钮
        document.querySelector("#saveNewDataBtn").addEventListener('click', saveNewData)
        // 条件查询form中的两个时间控件
        $('#birthdayStart').datetimepicker({});
        $('#birthdayEnd').datetimepicker({});
        // 查询按钮
        document.querySelector('#searchBtn').addEventListener('click', queryTestData1)
        document.querySelector('#searchBtn2').addEventListener('click', queryTestData2)
        document.querySelector('#reset').addEventListener('click', () => document.querySelector("#searchForm").reset())
        // 打开插入数据对话窗
        document.querySelector('#openInsertDemoBtn').addEventListener('click', openInsertDemo)
        document.querySelector('#resultTypeDemoBtn').addEventListener('click', resultTypeDemo)
        document.querySelector('#resultMapDemoBtn').addEventListener('click', resultMapDemo)
        // 查询数据
        setTimeout(function () {
            queryTestData1()
        }, 10);
        // 最基本的 ： 按名字查询按钮事件
        const searchUserNameBtn = document.querySelector('#searchUserNameBtn')
        searchUserNameBtn.addEventListener('click', searchByName)
    });

    function resultTypeDemo() {
        $.ajax("/user/queryAll", {
            type: 'get',
            success: function (res) {
                showResultAlert(res)
            },
            error: function () {
                bsAlert('出错了，请在后台查看代码及日志， 线程号：', 'danger')
            },
        })
    }

    function resultMapDemo() {
        $.ajax("/user/queryAll2", {
            type: 'get',
            success: function (res) {
                showResultAlert(res)
            },
            error: function () {
                bsAlert('出错了，请在后台查看代码及日志， 线程号：', 'danger')
            },
        })
    }

    function openDebugDialog() {
        if (!this.dialogInstance) {
            const dialog = document.querySelector("#debugDialog")
            dialog.addEventListener('hide.bs.modal', function () {
                addNewTestDataCanvas.hide()
            })
            this.dialogInstance = new bootstrap.Modal(dialog)
        }
        const dialog = this.dialogInstance
        dialog.show()
    }

    // 头像显示单元格
    function avatarCellRenderer(params) {
        if (!params.value) {
            return document.createTextNode('')
        }
        const img = document.createElement('img');
        img.src = 'data:image/jpg;base64,' + params.value
        img.style.height = '28px';
        img.style.width = '28px';
        return img

    }

    // 操作单元格
    function operationCellRender(params) {
        const dom = document.createElement('div')
        // getById 按钮
        const getOneBtn = document.createElement('button')
        getOneBtn.classList.add('btn', 'btn-link')
        getOneBtn.innerText = 'getById'
        getOneBtn.addEventListener('click', function () {
            $.ajax("/user/getById/" + params.data.id, {
                type: 'get',
                success: function (threadId) {
                    bsAlert('请在后台查看代码及日志， 线程号：' + threadId, 'success')
                },
                error: function () {
                    bsAlert('出错了，请在后台查看代码及日志', 'danger')
                },
            })
        });
        dom.appendChild(getOneBtn)

        // getUserInfoWithDetail
        const getUserInfoWithDetailBtn = document.createElement('button')
        getUserInfoWithDetailBtn.classList.add('btn', 'btn-link')
        getUserInfoWithDetailBtn.innerText = 'Demo1'
        getUserInfoWithDetailBtn.addEventListener('click', function () {
            $.ajax("/user/getUserInfoWithDetail/" + params.data.id, {
                type: 'get',
                success: function (response) {
                    showSingleUserResultAlert(response)
                },
                error: function () {
                    bsAlert('出错了，请在后台查看代码及日志.', 'danger')
                },
            })
        });
        dom.appendChild(getUserInfoWithDetailBtn)
        // getUserInfoWithDetail2
        const getUserInfoWithDetail2Btn = document.createElement('button')
        getUserInfoWithDetail2Btn.classList.add('btn', 'btn-link')
        getUserInfoWithDetail2Btn.innerText = 'Demo2'
        getUserInfoWithDetail2Btn.addEventListener('click', function () {
            $.ajax("/user/getUserInfoWithDetail2/" + params.data.id, {
                type: 'get',
                success: function (response) {
                    showSingleUserResultAlert(response)
                },
                error: function () {
                    bsAlert('出错了，请在后台查看代码及日志.', 'danger')
                },
            })
        });
        dom.appendChild(getUserInfoWithDetail2Btn)

        // getUser
        const getUser = document.createElement('button')
        getUser.classList.add('btn', 'btn-link')
        getUser.innerText = 'Demo3'
        getUser.addEventListener('click', function () {
            $.ajax("/user/getUser/" + params.data.id, {
                type: 'get',
                success: function (response) {
                    showSingleUserResultAlert(response)
                },
                error: function () {
                    bsAlert('出错了，请在后台查看代码及日志.', 'danger')
                },
            })
        });
        dom.appendChild(getUser)
        // getUser2
        const getUser2 = document.createElement('button')
        getUser2.classList.add('btn', 'btn-link')
        getUser2.innerText = 'Demo4'
        getUser2.addEventListener('click', function () {
            bsAlert('这是一个数据库关联查询懒加载的例子，看org.issaclu.springbootmybatis.user.service.UserInfoService.getUser2 这个方法',
                'danger')
            $.ajax("/user/getUser2/" + params.data.id, {
                type: 'get',
                success: function (response) {
                    showSingleUserResultAlert(response)
                },
                error: function () {
                    bsAlert('出错了，请在后台查看代码及日志.', 'danger')
                },
            })
        });
        dom.appendChild(getUser2)
        // getUserWithHouse
        const getUserWithHouse = document.createElement('button')
        getUserWithHouse.classList.add('btn', 'btn-link')
        getUserWithHouse.innerText = 'Demo5'
        getUserWithHouse.addEventListener('click', function () {
            $.ajax("/user/getUserWithHouse/" + params.data.id, {
                type: 'get',
                success: function (response) {
                    showSingleUserResultAlert(response)
                },
                error: function () {
                    bsAlert('出错了，请在后台查看代码及日志.', 'danger')
                },
            })
        });
        dom.appendChild(getUserWithHouse)

        // getUserWithHouse2
        const getUserWithHouse2 = document.createElement('button')
        getUserWithHouse2.classList.add('btn', 'btn-link')
        getUserWithHouse2.innerText = 'Demo6'
        getUserWithHouse2.addEventListener('click', function () {
            $.ajax("/user/getUserWithHouse2/" + params.data.id, {
                type: 'get',
                success: function (response) {
                    showSingleUserResultAlert(response)
                },
                error: function () {
                    bsAlert('出错了，请在后台查看代码及日志.', 'danger')
                },
            })
        });
        dom.appendChild(getUserWithHouse2)
        return dom
    }

    function showSingleUserResultAlert(response) {
        response.avatar = '太长了，不显示了。'
        showResultAlert(response)
    }

    function showResultAlert(response) {
        const newAlert = document.createElement('div');
        newAlert.classList.add('alert', 'alert-success', 'alert-dismissible')
        newAlert.innerHTML = document.querySelector("#oneToOneAlert").innerHTML;
        newAlert.querySelector('.alert-body').innerHTML = JSON.stringify(response, null, 2);
        bsAlert(newAlert, 'success')
    }

    // 初始化表格
    function initTestGrid() {
        const gridDiv = document.querySelector('#testGrid');

        const gridOptions = {
            columnDefs: [
                {field: "userName", headerName: "名字", width: 100},
                {field: "age", headerName: "年龄", width: 100},
                {
                    field: "birthday", headerName: "出生日期", width: 200, valueFormatter: function (param) {

                        const value = param.value
                        if (!value) {
                            return ''
                        }
                        const day = dayjs(value)
                        return day.format('YYYY-MM-DD HH:mm:ss')
                    }
                },
                {field: "introductionText", headerName: "介绍", minWidth: 50, flex: 1},
                {field: "avatar", headerName: "头像", width: 100, cellRenderer: avatarCellRenderer},
                {field: "", headerName: "操作", width: 600, cellRenderer: operationCellRender},
            ],
            rowSelection: 'multiple', // 是否可以多行同时选中
        }

        new agGrid.Grid(gridDiv, gridOptions);
        return gridOptions
    }

    // 保存数据
    function saveNewData() {
        const formDom = document.querySelector('#newDataForm');
        const form = document.querySelector('#newDataForm').elements;
        const formData = new FormData();
        formData.append('userName', form.userName.value)
        formData.append('age', form.age.value)
        formData.append('sex', form.sex.value)
        formData.append('introductionText', form.introductionText.value)
        const birthday = $("#birthday").datetimepicker('getValue');
        formData.append('birthday', birthday.toUTCString())
        formData.append('level', form.level.value)
        if (form.avatar.files[0]) {
            formData.append("avatar", form.avatar.files[0]);
        }
        $.ajax("/user-detail/insert", {
            type: 'post',
            data: formData,
            contentType: false,
            processData: false,
            success: function () {
                // 收起表彰
                addNewTestDataCanvas.hide()
                // 清空表单数据
                formDom.reset()
                // 重新查数据
                queryTestData1()
                bsAlert('成功', 'success')
            },
            error: function () {
                bsAlert('出错了', 'danger')
            },
        })
    }

    function openInsertDemo() {
        openDebugDialog()
        setTimeout(function (){
            addNewTestDataCanvas.show()
        }, 1000)

    }

    function queryTestData2(){
        queryTestData('/user/query')
    }
    function queryTestData1(){
        queryTestData('/user/select')
    }
    // 查询数据并填充到表格里
    function queryTestData(url) {
        const form = document.querySelector('#searchForm').elements;
        let start = undefined
        let end = undefined
        if (form.birthdayStart.value) {
            start = $(form.birthdayStart).datetimepicker('getValue').toUTCString();
        }
        if (form.birthdayEnd.value) {
            end = $(form.birthdayEnd).datetimepicker('getValue').toUTCString();
        }
        const param = {
            userName: form.searchUserName.value,
            introductionText: form.searchIntroduction.value,
            sex: form.searchSex.value || undefined,
            birthdayStart: start,
            birthdayEnd: end
        }

        gridApi.setRowData([])
        $.ajax(url, {
            type: 'post',
            dataType: 'json',
            contentType: 'application/json',
            data: JSON.stringify(param),
            success: function (data) {
                // 数据填充到表格
                gridApi.setRowData(data)
            },
            error: function () {
                bsAlert('查询数据出错', 'danger')
            },
        })
    }

    function searchByName() {
        const name = document.querySelector("#searchUserName").value;
        const pre = document.querySelector("#searchUserNamePre")
        pre.innerHTML = ''
        $.ajax("/user/getByName" + (name ? ("/" + name) : ''), {
            type: 'get',
            dataType: 'json',
            success: function (data) {
                pre.innerHTML = JSON.stringify(data, null, 2)
            },
            error: function () {
                bsAlert('查询数据出错', 'danger')
            }
        })
    }


</script>
</body>
</html>
